Profile picture

[JPA] 연관 관계 매핑

Amaranth2023년 07월 09일

연관 관계


엔티티들은 대부분 다른 엔티티와 연관 관계를 가지고 있다.

  • 객체는 참조를 사용하여 관계를 맺고, 테이블은 외래키를 사용해서 관계를 맺는다.

  • 연관관계에서 유심히 짚어봐야 할 포인트는 다음의 3가지이다.

    • 방향 : 단방향, 양방향(*테이블 관계는 항상 양방향임)

      • A에서 B의 정보에 접근할 수 있을 때, 연관 관계의 방향을 A→B로 표현한다.
    • 다중성 : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)

    • 연관관계의 주인 : 양방향 연관관계에서 연관관계의 주가 되는 엔티티(=외래키를 가지고 있는 엔티티)

      • 연관 관계의 주인만이 DB 연관 관계와 매핑되고 외래키를 등록/수정/삭제할 수 있다.(다른 쪽은 읽기만 가능)

      • 실제 DB 테이블의 다대일/일대다 관계에서는 ‘다’ 쪽이 외래키를 가진다.

@JoinColumn

: 연관 관계의 주인인 엔티티(=다대일/일대다에서 ‘다’에 해당하는 엔티티)에서 외래키를 매핑할 때 사용하는 어노테이션

  • name 속성 - 매핑할 외래 키 컬럼 명을 지정한다.(@Column의 name과 동일)
  • 일대다 단방향 관계를 매핑할 때는 필수적으로 사용해야 한다.

양방향 연관 관계의 mappedBy

양방향 연관 관계에 대해, 연관 관계의 주인이 아닌 엔티티(=다대일/일대다에서 ‘일’에 해당하는 엔티티)에서 연관 관계의 주인을 지정하는 속성이다.

@OneToOne, @OneToMany, @ManyToOne에서 사용할 수 있다.

  • 양방향 다대일/일대다 관계에서 해당 속성 값을 생략하면 중간 테이블이 생성된다.

  • 다대일/일대다 관계에 대해서는 후술하겠지만, mappedBy를 이해하기 위해 회원(Member)과 팀(Team)의 다대일 양방향 관계를 예시로 들어보겠다.

    class Member {
        @ManyToOne
        @JoinColumn(name = "TEAM_ID")
        private Team team;
    }
    
    class Team {
        @OneToMany(mappedBy="team")
        private List<Member> members = new ArrayList<>();
    }

    ‘다’에 해당하는 Member가 연관 관계의 주인이므로, 주인이 아닌 Team의 members 필드는 Member의 ‘team’ 필드(TEAM_ID 컬럼)에 매핑된다.

일대다(1:N) & 다대일(N:1) 연관 관계


다대일 관계와 일대다 관계를 이해하기 위해 한 가지 예시를 들어보자.

지하철역(Station)과 노선(Line)이 있을 때, 한 노선에 여러 개의 역이 소속될 수 있기 때문에

  • 지하철역을 기준으로 노선은 다대일(N:1) 관계에 있고,
  • 노선을 기준으로 지하철역은 일대다(1:N) 관계에 있다고 할 수 있다.

@ManyToOne & @OneToMany

  • @ManyToOne : 다대일 관계를 매핑하는 어노테이션
  • @OneToMany : 일대다 관계를 매핑하는 어노테이션

예제 - 역(Station)에서만 노선(Line) 정보에 접근할 수 있는(Station ⇒ Line) 다대일 단방향 연관관계

@Entity
@Table(name = "line")
public class Line {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

		@Column(nullable = false)
    private String name;

    ...
}
@Entity
@Table(name = "station")
public class Station {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

		@Column(nullable = false)
    private String name;

		@ManyToOne
		@JoinColumn(name = "line_id")
		private Line line;

    public void setLine(final Line line) {
				this.line = line;
    }

    ...
}

예제 - 역(Station)과 노선(Line) 둘 다 서로의 정보에 접근할 수 있는(Station↔Line) 다대일 양방향 연관 관계

*Station은 앞의 예제 코드에서의 것과 동일하다.

@Entity
@Table(name = "line")
public class Line {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

		@Column(nullable = false)
    private String name;

		@OneToMany(mappedBy = "line")
		private List<Station> stations = new ArrayList<>();

    ...
}

예제 - 노선(Line)에서만 지하철역(Station) 정보에 접근할 수 있는(Line ⇒ Station) 일대다 단방향 연관관계

@Entity
@Table(name = "line")
public class Line {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

		@Column(nullable = false)
    private String name;

		@OneToMany(name = "line_id")
		private List<Station> stations = new ArrayList<>();

    ...
}
@Entity
@Table(name = "station")
public class Station {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

		@Column(nullable = false)
    private String name;
    ...
}

일대일(1:1) 연관 관계


양쪽이 서로 하나씩만 가지는 관계

  • 예시 - 한 명의 학생이 한 개의 사물함만 소유할 수 있고, 사물함도 한 명의 학생에게 배정될 수 있는 관계.

@OneToOne

: 일대일 연관 관계를 매핑하는 어노테이션

일대일 관계는 반대도 일대일 관계이다. 그래서 주 테이블과 대상 테이블 중 어느 곳에든 외래 키를 둘 수 있기 때문에 어디에 외래 키를 둘 지에 고민해봐야 한다.

Student가 주 테이블, Locker가 대상 테이블이라고 가정해보자.

class Student {
    Long id;
    String name;
    Locker locker ;
}

class Locker {
    Long id;
}

주 테이블 Student에 외래 키를 두는 경우

주 테이블이 외래 키를 가지고 있기 때문에, Student ⇒ Locker의 방향성을 가진다.

  • Student ⇒ Locker의 방향성을 가진 단방향 연관 관계

    @Entity
    @Table(name = "student")
    public class Student{
    		@Id
    		@GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
    		@Column(name = "name", nullable = false)
        private String name;
    
    		@OneToOne
    		@JoinColumn(name = "locker_id")
    		private Locker locker;
    
        ...
    }
    @Entity
    @Table(name = "locker")
    public class Locker {
    		@Id
    		@GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        ...
    }
  • Student ↔ Locker의 방향성을 가진 양방향 연관 관계

    Locker에도 Student 참조를 추가해서 양방향으로 만들었다.

    @Entity
    @Table(name = "locker")
    public class Locker {
    		@Id
    		@GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
    		@OneToOne(mappedBy = "locker")
    		private Student student;
    
        ...
    }

대상 테이블(Locker)에 외래키를 두는 경우

이 경우, 테이블 관계가 일대일에서 일대다로 변경될 때 테이블 구조를 그대로 유지할 수 있다는 장점이 있다.

*이 경우 단방향 연관 관계는 만들 수 없다.

  • 양방향 연관 관계

    @Entity
    public class Locker {
    
    	@Id @GeneratedValue
    	@Column(name = "LOCKER_ID")
    	private Long id;
    
    	private String name;
    
    	@OneToOne
    	@JoinColumn(name = "STUDENT_ID")
    	private Student student;
    @Entity
    public class Student {
    
    	@Id @GeneratedValue
    	@Column(name = "STUDENT_ID")
    	private Long id;
    
    	private String userName;
    
    	@OneToOne(mappedBy = "student")
    	private Locker locker;

다대다(M:N) 연관관계


: 두 개의 테이블이 서로의 행에 대해 여러 개로 연관되어 있는 상태를 다대다(M:N) 관계라고 한다.

  • 예시 - 한 명의 학생이 여러 개의 수업을 수강할 수 있고, 한 수업이 여러 명의 학생을 수용할 수 있는 관계.

@ManyToMany

: 다대다 관계를 매핑해주는 어노테이션

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
    • 즉, 다대다 관계를 일대다-다대일 관계를 풀어주는 연결 테이블이 필수적이다.
  • @ManyToMany 어노테이션은 연결 테이블에 키를 제외한 필드가 추가되면 사용할 수 없다.

참고 자료


[JPA] - @JoinColumn과 연관관계의 주인 (mappedBy)

JPA - 다양한 연관관계 - 1 : 1

@JoinColumn vs mappedBy

우테코 강의자료(LMS)


Loading script...